/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.helper;

import java.util.Iterator;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Pseudostate;
import org.eclipse.uml2.uml.PseudostateKind;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.State;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.Vertex;

import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.statemachine.Activator;
import com.cea.papyrus.diagram.statemachine.preference.StateMachineDiagramPreferenceConstants;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * Manages Pseudostate.
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class NodePseudostateHelper extends NodeHelper{

	public final static String PSEUDOSTATE_DISPLAY_HORIZONTAL = "PSEUDOSTATE_DISPLAY_HORIZONTAL";

	public final static String PSEUDOSTATE_DISPLAY_VERTICAL = "PSEUDOSTATE_DISPLAY_VERTICAL";

	public final static String PSEUDOSTATE_DISPLAY = "PSEUDOSTATE_DISPLAY";

	/**
	 * The channel.
	 */
	private int channel = 501;

	/**
	 * The default container when used in state machine. 
	 */
	private GraphElement defaultContainerForStateMachineOwned;

	/**
	 * The default container when used in composite state. 
	 */
	private GraphElement defaultContainerForCompositeStateOwned;

	/**
	 * The associated PseudostateKind.
	 */
	protected PseudostateKind kind = null;

	/**
	 * The default constructor.
	 */
	public NodePseudostateHelper(){
		super();
		Debug.debug(this,"Call NodePseudoStateHelper()", channel); //$NON-NLS-1$
	}

	/**
	 * The constructor of a given kind of Pseudostates.
	 * 
	 * @param kind the kind of the Pseudostate to create
	 */
	public NodePseudostateHelper(PseudostateKind kind){
		super();
		Debug.debug(this,"Call NodePseudoStateHelper(PseudoStateKind kind)", channel); //$NON-NLS-1$
		this.kind = kind;
	}

	/**
	 * 
	 * 
	 * @param umlChild 
	 * @param size 
	 * @param location 
	 * @param parent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createGraphElement(org.eclipse.uml2.uml.Element, com.cea.papyrus.diagraminterchange2.di2.GraphElement, org.eclipse.draw2d.geometry.Point, org.eclipse.draw2d.geometry.Dimension)
	 */
	@Override
	public GraphNode createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size){
		Debug.debug(this,"Call GraphNode NodePseudoStateHelper.createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size)", channel); //$NON-NLS-1$

		//caution: must pass a null argument for umlParent to the super class method
		//otherwise the graph node is directly added!!
		//we adjust the size to the strict dimension of the icon figure
		if((size == null) || size.equals(getGraphNodeSize()))
			size = new Dimension(16, 16);
		GraphNode graphNode = super.createGraphElement(umlChild, null, location, size);
		//set display property
		Property property = graphNode.getProperty(PSEUDOSTATE_DISPLAY);
		if(property == null){
			//create a property for this graph node to hold display property
			property = Di2Factory.eINSTANCE.createProperty();
			property.setKey(PSEUDOSTATE_DISPLAY);
			property.setValue(PSEUDOSTATE_DISPLAY_HORIZONTAL);		
			graphNode.getProperty().add(property);
		} 
		else
			property.setValue(PSEUDOSTATE_DISPLAY_HORIZONTAL);	

		kind = ((Pseudostate)umlChild).getKind();
		if((kind != null) && (kind.equals(PseudostateKind.ENTRY_POINT_LITERAL) || (kind.equals(PseudostateKind.EXIT_POINT_LITERAL)))){
			if(((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement() instanceof Region)
				parent = (GraphElement)parent.getSemanticParent();
			
			if(umlChild.getOwner() instanceof State)
				defaultContainerForCompositeStateOwned = parent.getContainer();
			else
				defaultContainerForStateMachineOwned = parent.getContainer();
			//to say that the point belong to entity parent
			graphNode.setSemanticParent(parent);

			//adjust the position
			//all calculus are made in absolute positions
			Point newpos = new Point(2,0);
			if(location != null)
				newpos = LookForElement.calculateAbsolutePosition((GraphNode)parent, location);
			else
				newpos = parent.getPosition();
			
			// set correct position: calculate position for the connection point reference
			graphNode.setPosition(inRunner((GraphNode)parent, newpos, graphNode.getSize()));

			Point origin = LookForElement.calculateAbsolutePosition(((GraphNode)parent.getContainer()), new Point(0,0));
			graphNode.setPosition(graphNode.getPosition().getTranslated(origin.getNegated().getCopy()));

			GraphNode labelNode = super.createGraphElement(null, null, new Point(graphNode.getPosition().x+18, graphNode.getPosition().y), null);
			labelNode.setSemanticParent(graphNode);
			labelNode.setIsVisible(false);

			//the ge parent is connected in last because an event may be sent at this moment and trigger a refresh.
			if((parent != null) && (parent.getContainer() != null)){
				parent.getContainer().getContained().add(graphNode);
				parent.getContainer().getContained().add(labelNode);
			}
		}
		else{
			//adjust the position so that node is centered beneath the mouse pointer
			Point centeredLocation = graphNode.getPosition().getCopy();
			Dimension dims = graphNode.getSize().getCopy();
			if(size.equals(16, 16))
				centeredLocation.translate(-dims.width/2, -dims.height/2);
			graphNode.setPosition(centeredLocation.getCopy());

			GraphNode labelNode = super.createGraphElement(null, null, new Point(centeredLocation.x,centeredLocation.y+graphNode.getSize().height+5), null);
			labelNode.setSemanticParent(graphNode);
			labelNode.setIsVisible(false);
			//the ge parent is connected in last because an event may be sent at this moment and trigger a refresh.
			if(parent != null){
				parent.getContained().add(graphNode);
				parent.getContained().add(labelNode);
			}
		}
		return graphNode;
	}

	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public boolean removeGraphElement(GraphElement child, GraphElement parent){
		Pseudostate pseudo = (Pseudostate)((Uml1SemanticModelBridge)child.getSemanticModel()).getElement();
		if(pseudo.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL) || pseudo.getKind().equals(PseudostateKind.EXIT_POINT_LITERAL)){
			// Save the default container for undo command...
			if(pseudo.getOwner() instanceof State)
				defaultContainerForCompositeStateOwned = child.getContainer();
			else
				defaultContainerForStateMachineOwned = child.getContainer();
			child.setSemanticParent(null);
			child.eAdapters().clear();
			Iterator<DiagramElement> itchild=child.getSemanticChild().iterator();
			for(int i=0; i<child.getSemanticChild().size();){
				GraphElement subchild=(GraphElement)child.getSemanticChild().get(i);
				subchild.eAdapters().clear();
				subchild.setSemanticParent(null);
				if(pseudo.getOwner() instanceof State)
					defaultContainerForCompositeStateOwned.getContained().remove(subchild);
				else
					defaultContainerForStateMachineOwned.getContained().remove(subchild);
			}
			if(pseudo.getOwner() instanceof State)
				defaultContainerForCompositeStateOwned.getContained().remove(child);
			else
				defaultContainerForStateMachineOwned.getContained().remove(child);
			return true;
		}
		else return super.removeGraphElement(child, parent);
	}
	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public boolean undoCreateGraphElement(GraphElement child, GraphElement parent){
		child.setSemanticParent(null);
		child.eAdapters().clear();
		Iterator<DiagramElement> itchild=child.getSemanticChild().iterator();
		for(int i=0; i<child.getSemanticChild().size();){
			GraphElement subchild=(GraphElement)child.getSemanticChild().get(i);
			subchild.eAdapters().clear();
			subchild.setSemanticParent(null);
			subchild.getContainer().getContained().remove(subchild);
		}
		child.getContainer().getContained().remove(child);
		return true;
	}

	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public GraphElement addGraphElement(GraphElement child, GraphElement parent){
		Pseudostate pseudo = (Pseudostate)((Uml1SemanticModelBridge)child.getSemanticModel()).getElement();
		if(pseudo.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL) || pseudo.getKind().equals(PseudostateKind.EXIT_POINT_LITERAL)){
			GraphElement oldParent = (GraphElement)child.getSemanticParent();
			child.setSemanticParent(parent);
			if(child.getSemanticParent().getContainer() != null) {
				child.getSemanticParent().getContainer().getContained().add(child);
			}
			else{
				if(pseudo.getOwner() instanceof State)
					defaultContainerForCompositeStateOwned.getContained().add(child);
				else
					defaultContainerForStateMachineOwned.getContained().add(child);
			}
			return child;
		}
		else return super.addGraphElement(child, parent);
	}

	/**
	 * 
	 * 
	 * @param parent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateGraphElement(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public boolean canCreateGraphElement(GraphElement parent){
		Debug.debug(this,"Call boolean NodePseudoStateHelper.canCreateGraphElement(GraphElement parent)", channel);

		if(parent.getSemanticModel() != null){
			Element umlParent = ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
			if(umlParent instanceof Region)
				return true;
		}
		return false;
	}

	///////////////////////////////////////////////////////////////////////////
	// UML Management
	///////////////////////////////////////////////////////////////////////////

	/**
	 * 
	 * 
	 * @param umlParent 
	 * @param name 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createUMLElement(java.lang.String, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public  Element createUMLElement(String name, Element umlParent){
		Debug.debug(this,"Call Element NodePseudoStateHelper.createUMLElement(String name, Element umlParent)", channel); //$NON-NLS-1$
		Pseudostate aState = UMLFactory.eINSTANCE.createPseudostate();
		aState.setKind(kind);
		aState.setName(name);
		if(umlParent instanceof Region)
			((Region)umlParent).getSubvertices().add(aState);
		else if(umlParent instanceof State)
			((State)umlParent).getConnectionPoints().add(aState);
		else if(umlParent instanceof StateMachine)
			((StateMachine)umlParent).getConnectionPoints().add(aState);
		return aState;
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateUMLElement(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean canCreateUMLElement(Element umlParent){
		Debug.debug(this,"Call boolean NodePseudoStateHelper.canCreateUMLElement(Element umlParent)", channel); //$NON-NLS-1$
		if(umlParent instanceof Region) 
			return true;
		//all other cases
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#addUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean addUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call boolean NodePseudoStateHelper.addUMLElement(Element element, Element umlParent)", channel); //$NON-NLS-1$
		if(umlParent instanceof Region)
			return ((Region)umlParent).getSubvertices().add((Vertex)element);
		else if(umlParent instanceof State)
			return ((State)umlParent).getConnectionPoints().add((Pseudostate)element);
		else if(umlParent instanceof StateMachine)
			return ((StateMachine)umlParent).getConnectionPoints().add((Pseudostate)element);
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#removeUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean removeUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call boolean NodePseudoStateHelper.removeUMLElement(Element element, Element umlParent)", channel); //$NON-NLS-1$
		if(umlParent instanceof Region)
			return ((Region)umlParent).getSubvertices().remove(element);
		else if(umlParent instanceof State)
			return ((State)umlParent).getConnectionPoints().remove(element);
		else if(umlParent instanceof StateMachine)
			return ((StateMachine)umlParent).getConnectionPoints().remove(element);
		return false;
	}

	/**
	 * 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getBaseString()
	 */
	@Override
	public String getBaseString(){
		if(kind != null){
			String name = kind.getName();
			return name.substring(0, 1).toUpperCase()+name.substring(1)+"_";
		}
		else
			return "PseudoState_";//$NON-NLS-1$
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getNewUMLElementName(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public String getNewUMLElementName(Element umlParent){
		String name = ""; //$NON-NLS-1$

		//i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			name = getBaseString()+i;
			if(umlParent == null)
				return name;
			if(umlParent instanceof Region){
				if(((Region)umlParent).getSubvertex(name) == null)
					return name;
			}
			else if(umlParent instanceof State){
				if(((State)umlParent).getConnectionPoint(name) == null)
					return name;
			}
			else if(umlParent instanceof StateMachine){
				if(((StateMachine)umlParent).getConnectionPoint(name) == null)
					return name;
			}
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}

	/**
	 * this method calculate the good position of an entry/exit point by taking account of his parent.
	 * 
	 * @param portDimension the dimension of the entry/exit point 
	 * @param parentGP graphNode the virtual graphNode that contains the entry/exit point 
	 * @param point the absolute position
	 * 
	 * @return the new position of the entry/exit point reference in absolute coordinate
	 */
	public Point inRunner(GraphNode parentGP, Point point, Dimension portDimension){
		int x = point.x;
		int y = point.y;
		Point selected = point.getCopy();
		//calculate absolute position of GP
		Point positionGP = LookForElement.calculateAbsolutePosition(parentGP, new Point(0,0));


		// position is constraint in the parent rectangle
		if (x<positionGP.x-portDimension.width/2){
			x=positionGP.x-portDimension.width/2;
		}
		if (x>positionGP.x+parentGP.getSize().width-portDimension.width/2){
			x=positionGP.x+parentGP.getSize().width-portDimension.width/2;
		}

		if (y<positionGP.y-portDimension.height/2){
			y=positionGP.y-portDimension.height/2;
		}
		if (y>positionGP.y+parentGP.getSize().height-portDimension.height/2){
			y=positionGP.y+parentGP.getSize().height-portDimension.height/2;
		}

		//calculate all 4 possible positions.		
		Point pointWEST= new Point(positionGP.x-portDimension.width/2, y);
		Point pointNORTH= new Point(x,positionGP.y-portDimension.height/2);
		Point pointEAST= new Point(positionGP.x+parentGP.getSize().width-portDimension.width/2, y);
		Point pointSOUTH= new Point(x,positionGP.y+parentGP.getSize().height-portDimension.height/2);

		// calculate all distance
		double distanceWEST=pointWEST.getDistance(point);
		double distanceNORTH=pointNORTH.getDistance(point);
		double distanceEAST=pointEAST.getDistance(point);
		double distanceSOUTH=pointSOUTH.getDistance(point);
		Debug.debug(this,"pointWEST: "+pointWEST+" "+distanceWEST,channel);
		Debug.debug(this,"pointNORTH: "+pointNORTH+" "+distanceNORTH,channel);
		Debug.debug(this,"pointEAST: "+pointEAST+" "+distanceEAST,channel);
		Debug.debug(this,"pointSOUTH: "+pointSOUTH+" "+distanceSOUTH,channel);

		//choose the point the minimum distance
		selected=pointWEST;
		double selecteddistance=distanceWEST;
		if (selecteddistance>distanceNORTH){
			selected=pointNORTH;
			selecteddistance=distanceNORTH;}

		if (selecteddistance>distanceEAST){
			selected=pointEAST;
			selecteddistance=distanceEAST;}

		if (selecteddistance>distanceSOUTH){
			selected=pointSOUTH;
			selecteddistance=distanceSOUTH;}
		Debug.debug(this,"miminmum distance: "+selected+" "+selecteddistance,channel);


		return selected;
	}



	/**
	 * 
	 * 
	 * @param graphElement 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#setGraphElementPreferences(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public void setGraphElementPreferences(GraphElement graphElement){
		//get the preference store
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();

		//set colors and font
		graphElement.setBackgroundColor(StateMachineDiagramPreferenceConstants.getPreferencePseudoStateBackgroundColor(store));
		graphElement.setBorderColor(StateMachineDiagramPreferenceConstants.getPreferencePseudoStateBorderColor(store));
		graphElement.setFontColor(StateMachineDiagramPreferenceConstants.getPreferencePseudoStateFontColor(store));
		FontData[] fontdata = StateMachineDiagramPreferenceConstants.getPreferencePseudoStateFont(store);
		if (fontdata.length>0){
			graphElement.setFontFamily(fontdata[0].getName());
			graphElement.setFontSize(fontdata[0].getHeight());
		}
	}
}